#include "private/gc_priv.h"

#if defined(GC_WIN32_THREADS)
#include <windows.h>

#ifdef CYGWIN32
#include <errno.h>

#undef pthread_create
#undef pthread_sigmask
#undef pthread_join
#undef pthread_detach
#undef dlopen

#define DEBUG_CYGWIN_THREADS 0

void* GC_start_routine(void* arg);
void GC_thread_exit_proc(void* arg);
#endif

typedef LONG* IE_t;

#ifndef MAX_THREADS
#define MAX_THREADS 256
#endif

GC_bool GC_thr_initialized = FALSE;

DWORD GC_main_thread = 0;

struct GC_thread_Rep
{
	LONG in_use;
	DWORD id;
	HANDLE handle;
	ptr_t stack_base;
	GC_bool suspended;

#ifdef CYGWIN32
	void* status;
	pthread_t pthread_id;
	short flags;
#define FINISHED 1
#define DETACHED 2
#endif
};

typedef volatile struct GC_thread_Rep* GC_thread;

volatile GC_bool GC_please_stop = FALSE;

volatile struct GC_thread_Rep thread_table[MAX_THREADS];

volatile LONG GC_max_thread_index = 0;

extern LONG WINAPI GC_write_fault_handler(struct _EXCEPTION_POINTERS* exc_info);

int GC_thread_is_registered(void)
{
#if defined(GC_DLL) || defined(GC_INSIDE_DLL)
	return 1;
#else
	return 0;
#endif
}

static GC_thread GC_new_thread(void)
{
	int i;
#if defined(MPROTECT_VDB)
	if (GC_incremental)
		SetUnhandledExceptionFilter(GC_write_fault_handler);
#endif

	for (i = 0; InterlockedExchange((IE_t)&thread_table[i].in_use, 1) != 0; i++)
	{
		if (i == MAX_THREADS - 1)
			ABORT("too many threads");
	}

	while(i > GC_max_thread_index)
	{
		InterlockedIncrement((IE_t)&GC_max_thread_index);
	}
	if (GC_max_thread_index >= MAX_THREADS)
	{
		GC_max_thread_index = MAX_THREADS - 1;
	}

#ifdef CYGWIN32
	thread_table[i].pthread_id = pthread_self();
#endif
	if (!DuplicateHandle(GetCurrentProcess(),
					GetCurrentThread(),
					GetCurrentProcess(),
					(HANDLE*)&thread_table[i].handle,
					0,
					0,
					DUPLICATE_SAME_ACCESS))
	{
		DWORD last_error = GetLastError();
		GC_printfl("Last error code: %lx\n", last_error);
		ABORT("DuplicateHandle failed");
	}
	thread_table[i].stack_base = GC_get_stack_base();
	if (thread_table[i].stack_base == NULL)
		ABORT("Failed to find stack base in GC_new_thread");
	thread_table[i].id = GetCurrentThreadId();
	while(GC_please_stop)
		Sleep(20);
	return thread_table + i;
}

#ifdef __GNUC__
__inline__
#endif
LONG GC_get_max_thread_index()
{
	LONG my_max = GC_max_thread_index;

	if (my_max >= MAX_THREADS)
		return MAX_THREADS - 1;
	return my_max;
}

static void GC_delete_gc_thread(GC_thread thr)
{
	CloseHandle(thr->handle);
	thr->stack_base = 0;
	thr->id = 0;
#ifdef CYGWIN32
	thr->pthread_id = 0;
#endif
	thr->in_use = FALSE;
}

static void GC_delete_thread(DWORD thread_id)
{
	int i;
	LONG my_max = GC_get_max_thread_index();

	for (i = 0; i <= my_max &&
		(!thread_table[i].in_use || thread_table[i].id != thread_id);
		i++) {};
	if (i > my_max)
	{
		WARN("Removing nonexistent thread %ld\n", (GC_word)thread_id);
	}
	else
	{
		GC_delete_gc_thread(thread_table + i);
	}
}

#ifdef CYGWIN32
static GC_thread GC_lookup_thread(pthread_t id)
{
	int i;
	LONG my_max = GC_get_max_thread_index();

	for (i = 0; 
		i < my_max &&
		(!thread_table[i].in_use || thread_table[i].pthread_id != id
		|| !thread_table[i].in_use);
		i++);
	if (i > my_max) return 0;
	return thread_table + i;
}
#endif

void GC_push_thread_structures GC_PROTO((void))
{
#ifdef CYGWIN32
	{
		int i;
		LONG my_max = GC_get_max_thread_index();

		for (i = 0; i <= my_max; i++)
			if (thread_table[i].in_use)
				GC_push_all((ptr_t)&(thread_table[i].status),
					(ptr_t)(&(thread_table[i].status) + 1));
	}
#endif
}

extern CRITICAL_SECTION GC_write_cs;

void GC_stop_world()
{
	DWORD thread_id = GetCurrentThreadId();
	int i;

	if (!GC_thr_initialized) ABORT("GC_stop_world() called before GC_thr_init()");

	GC_please_stop = TRUE;
#ifndef CYGWIN32
	EnterCriticalSection(GC_write_cs);
#endif
	for (i = 0; i <= GC_get_max_thread_index(); i++)
		if (thread_table[i].stack_base != 0
			&& thread_table[i].id != thread_id)
		{
#ifdef MSWINCE
			while(SuspendThread(thread_table[i].handle) == (DWORD)-1)
				Sleep(10);
#else
			DWORD exitCode;
			if (GetExitCodeThread(thread_table[i].handle, &exitCode) &&
				exitCode != STILL_ACTIVE)
			{
				thread_table[i].stack_base = 0;
#ifndef CYGWIN32
				thread_table[i].in_use = FALSE;
				CloseHandle(thread_table[i].handle);
#endif
				continue;
			}
			if (SuspendThread(thread_table[i].handle) == (DWORD)-1)
			{
				thread_table[i].stack_base = 0;
#ifndef CYGWIN32
				thread_table[i].in_use = FALSE;
				CloseHandle(thread_table[i].handle);
#endif
			}
#endif
			thread_table[i].suspended = TRUE;
		}
#ifndef CYGWIN32
		LeaveCriticalSection(&GC_write_cs);
#endif
}

void GC_start_world()
{
	DWORD thread_id = GetCurrentThreadId();
	int i;
	LONG my_max = GC_get_max_thread_index();

	for (i = 0; i <= my_max; i++)
		if (thread_table[i].stack_base != 0 && thread_table[i].suspended
			&& thread_table[i].id != thread_id)
		{
			if (ResumeThread(thread_table[i].handle) == (DWORD)-1)
				ABORT("ResumeThread failed");
			thread_table[i].suspended = FALSE;
		}
	GC_please_stop = FALSE;
}

#ifdef _MSC_VER
#pragma warning(disable:4715)
#endif
ptr_t GC_current_stackbottom()
{
	DWORD thread_id = GetCurrentThreadId();
	int i;
	LONG my_max = GC_get_max_thread_index();

	for (i = 0; i <= my_max; i++)
		if (thread_table[i].stack_base && thread_table[i].id == thread_id)
			return thread_table[i].stack_base;
	ABORT("no thread table entry for current thread");
}
#ifdef _MSC_VER
#pragma warning(default:4715)
#endif

#ifdef MSWINCE
#define GC_get_stack_min(s) \
	((ptr_t)(((DOWRD)(s) - 1) & 0xFFFF0000))
#else
	static ptr_t GC_get_stack_min(ptr_t s)
	{
		ptr_t bottom;
		MEMORY_BASIC_INFORMATION info;
		VirtualQuery(s, &info, sizeof(info));
		do 
		{
			bottom = info.BaseAddress;
			VirtualQuery(bottom - 1, &info, sizeof(info));
		} while ((info.Protect & PAGE_READWRITE)
			&& !(info.Protect & PAGE_GUARD));
		return bottom;
	}
#endif

void GC_push_all_stacks()
{
	DWORD thread_id = GetCurrentThreadId();
	GC_bool found_me = FALSE;
	int i;
	int dummy;
	ptr_t sp, stack_min;
	GC_thread thread;
	LONG my_max = GC_get_max_thread_index();

	for (i = 0; i <= my_max; i++)
	{
		thread = thread_table + i;
		if (thread->in_use && thread->stack_base)
		{
			if (thread->id == thread_id)
			{
				sp = (ptr_t)&dummy;
				found_me = TRUE;
			}
			else
			{
				CONTEXT context;
				context.ContextFlags = CONTEXT_INTEGER | CONTEXT_CONTROL;
				if (!GetThreadContext(thread_table[i].handle, &context))
					ABORT("GetThreadContext failed");

#define PUSH1(reg) GC_push_one((word)context.reg)
#define PUSH2(r1, r2) PUSH1(r1), PUSH1(r2)
#define PUSH4(r1, r2, r3, r4) PUSH2(r1, r2), PUSH2(r3, r4)
#if defined(I386)
				PUSH4(Edi, Esi, Ebx, Edx), PUSH2(Ecx, Eax), PUSH1(Ebp);
				sp = (ptr_t)context.Esp;
#elif defined(X86_64)
				PUSH4(Rax, Rcx, Rdx, Rbx); PUSH2(Rbp, Rsi); PUSH1(Rdi);
				PUSH4(R8, R9, R10, R11); PUSH4(R12, R13, R14, R15);
				sp = (ptr_t)context.Rsp;
#elif defined(ARM32)
				PUSH4(R0, R1, R2, R3), PUSH4(R4, R5, R6, R7), PUSH4(R8, R9, R10, R11), PUSH1(R12);
				sp = (ptr_t)context.Sp;
#elif defined(SHx)
				PUSH4(R0, R1, R2, R3), PUSH4(R4, R5, R6, R7), PUSH4(R8, R9, R10, R11);
				PUSH2(R12, R13), PUSH1(R14);
				sp = (ptr_t)context.R15;
#elif defined(MIPS)
				PUSH4(IntAt, IntV0, IntV1, IntA0), PUSH4(IntA1, IntA2, IntA3, IntT0);
				PUSH4(IntT1, IntT2, IntT3, IntT4), PUSH4(IntT5, IntT6, IntT7, IntS0);
				PUSH4(IntS1, IntS2, IntS3, IntS4), PUSH4(IntS5, IntS6, IntS7, IntT8);
				PUSH4(IntT9, IntK0, IntK1, IntS8);
				sp = (ptr_t)context.IntSp;
#elif defined(PPC)
				PUSH4(Gpr0, Gpr3, Gpr4, Gpr5), PUSH4(Gpr6, Gpr7, Gpr8, Gpr9);
				PUSH4(Gpr10, Gpr11, Gpr12, Gpr14), PUSH4(Gpr15, Gpr16, Gpr17, Gpr18);
				PUSH4(Gpr19, Gpr20, Gpr21, Gpr22), PUSH4(Gpr23, Gpr24, Gpr25, Gpr26);
				PUSH4(Gpr27, Gpr28, Gpr29, Gpr30), PUSH4(Gpr31);
				sp = (ptr_t)context.Gpr1;
#elif defined(ALPHA)
				PUSH4(IntV0, IntT0, IntT1, IntT2), PUSH4(IntT3, IntT4, IntT5, IntT6);
				PUSH4(IntT7, IntS0, IntS1, IntS2), PUSH4(IntS3, IntS4, IntS5, IntFp);
				PUSH4(IntA0, IntA1, IntA2, IntA3), PUSH4(IntA4, IntA5, IntT8, IntT9);
				PUSH4(IntT10, IntT11, IntT12, IntAt);
				sp = (ptr_t)context.IntSp;
#else
#error "architecture is not supported"
#endif
			}

			stack_min = GC_get_stack_min(thread->stack_base);

			if (sp >= stack_min && sp < thread->stack_base)
				GC_push_all_stacks(sp, thread->stack_base);
			else
			{
				WARN("Thread stack pointer 0x%lx out of range, pushing everything\n",
					(unsigned long)sp);
				GC_push_all_stacks(stack_min, thread->stack_base);
			}
		}
	}
	if (!found_me) ABORT("Collecting from unknown thread.");
}

void GC_get_next_stack(char* start, char** lo, char** hi)
{
	int i;
#define ADDR_LIMIT (char*)(-1L)
	char* current_min = ADDR_LIMIT;
	LONG my_max = GC_get_max_thread_index();

	for (i = 0; i <= my_max; i++)
	{
		char* s = (char*)thread_table[i].stack_base;

		if (0 != s && s > start && s < current_min)
		{
			current_min = s;
		}
	}
	*hi = current_min;
	if (current_min == ADDR_LIMIT)
	{
		*lo = ADDR_LIMIT;
		return;
	}
	*lo = GC_get_stack_min(current_min);
	if (*lo < start)
		*lo = start;
}

#if !defined(CYGWIN32)
#if !defined(MSWINCE) && defined(GC_DLL)
GC_API HANDLE WINAPI GC_CreateThread(
	LPSECURITY_ATTRIBUTES lpThreadAttributes,
	DWORD dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress,
	LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId)
{
	return CreateThread(lpThreadAttributes, dwStackSize, lpStartAddress,
		lpParameter, dwCreationFlags, lpThreadId);
}
#else
typedef struct
{
	LPTHREAD_START_ROUTINE start;
	LPVOID param;
} thread_args;

static DWORD WINAPI thread_start(LPVOID arg);

GC_API HANDLE WINAPI GC_CreateThread(
	LPSECURITY_ATTRIBUTES lpThreadAttributes,
	DWORD dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress,
	LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId)
{
	HANDLE thread_h = NULL;

	thread_args* args;
	if (!GC_is_initialized) GC_init();

	args = GC_malloc_uncollectable(sizeof(thread_args));
	if (0 == args)
	{
		SetLastError(ERROR_NOT_ENOUGH_MEMORY);
		return NULL;
	}

	args->start = lpStartAddress;
	args->param = lpParameter;

	thread_h = CreateThread(lpThreadAttributes,
		dwStackSize, thread_start,
		args, dwCreationFlags,
		lpThreadId);

	return thread_h;
}

static DWORD WINAPI thread_start(LPVOID arg)
{
	DWORD ret = 0;
	thread_args* args = (thread_args*)arg;

	GC_new_thread();
#ifndef __GNUC__
	__try{
#endif
	ret = args->start(args->param);
#ifndef __GNUC__
	} __finally {
#endif
	GC_free(args);
	GC_delete_thread(GetCurrentThreadId());
#ifndef __GNUC__
	}
#endif

	return ret;
}
#endif
#endif

#ifdef MSWINCE
typedef struct  
{
	HINSTANCE hInstance;
	HINSTANCE hPrevInstance;
	LPWSTR lpCmdLine;
	int nShowCmd;
} main_thread_args;

DWORD WINAPI main_thread_start(LPVOID arg);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
	LPWSTR lpCmdLine, int nShowCmd)
{
	DWORD exit_code = 1;

	main_thread_args args = 
	{
		hInstance, hPrevInstance, lpCmdLine, nShowCmd
	};
	HANDLE thread_h;
	DWORD thread_id;

	GC_init();

	thread_h = GC_CreateThread(NULL, 0, main_thread_start, &args, 0, &thread_id);

	if (thread_h != NULL)
	{
		WaitForSingleObject(thread_h, INFINITE);
		GetExitCodeThread(thread_h, &exit_code);
		CloseHandle(thread_h);
	}

	GC_deinit();
	DeleteCriticalSection(&GC_allocate_ml);

	return (int)exit_code;
}

DWORD WINAPI main_thread_start(LPVOID arg)
{
	main_thread_args* args = (main_thread_args*)arg;

	return (DWORD)GC_WinMain(args->hInstance, args->hPrevInstance,
				args->lpCmdLine, args->nShowCmd);
}
#else
void GC_thr_init()
{
	if (GC_thr_initialized) return;
	GC_main_thread = GetCurrentThreadId();
	GC_thr_initialized = TRUE;

	GC_new_thread();
}

#ifdef CYGWIN32
struct start_info
{
	void* (*start_routine)(void*);
	void* arg;
	GC_bool detached;
};

int GC_pthread_join(pthread_t pthread_id, void** retvalue)
{
	int result;
	int i;
	GC_thread me;

#if DEBUG_CYGWIN_THREADS
	GC_printf3("thread 0x%x(0x%x) is joining thread 0x%x.\n",
		(int)pthread_self(), GetCurrentThreadId(), (int)pthread_id);
#endif

	while ((me == GC_lookup_thread(pthread_id)) == 0) Sleep(10);

	result = pthread_join(pthread_id, retvalue);

	GC_delete_gc_thread(me);

#if DEBUG_CYGWIN_THREADS
	GC_printf3("thread 0x%x(0x%x) completed join with thread 0x%x.\n",
		(int)pthread_self(), GetCurrentThreadId(), (int)pthread_id);
#endif

	return result;
}

int
GC_pthread_create(pthread_t* new_thread,
	const pthread_attr_t* attr,
	void *(*start_routine)(void*), void* arg)
{
	int result;
	struct start_info* si;

	if (!GC_is_initialized) GC_init();
	si = GC_malloc_uncollectable(sizeof(struct start_info));
	if (0 == si) return (EAGAIN);

	si->start_routine = start_routine;
	si->arg = arg;
	if (attr != 0 &&
		pthread_attr_getdetachstate(attr, &si->detached)
		== PTHREAD_CREATE_DETACHED)
	{
		si->detached = true;
	}

#if DEBUG_CYGWIN_THREADS
	GC_printf2("About to create a thread from 0x%x(0x%x)\n",
		(int)pthread_self(), GetCurrentThreadId());
#endif
	result = pthread_create(new_thread, attr, GC_start_routine, si);

	if (result)
	{
		GC_free(si);
	}

	return (result);
}

void* GC_start_routine(void* arg)
{
	struct start_info* si = arg;
	void* result;
	void* (*start)(void*);
	void* start_arg;
	pthread_t pthread_id;
	GC_thread me;
	GC_bool detached;
	int i;

#if DEBUG_CYGWIN_THREADS
	GC_printf2("thread 0x%x(0x%x) starting...\n", (int)pthread_self(),
		GetCurrentThreadId());
#endif

	LOCK();
	me = GC_new_thread();
	UNLOCK();

	start = si->start_routine;
	start_arg = si->arg;
	if (si->detached)
		me->flags |= DETACHED;
	me->pthread_id = pthread_id = pthread_self();

	GC_free(si);

	pthread_cleanup_push(GC_thread_exit_proc, (void*)me);
	result = (*start)(start_arg);
	me->status = result;
	pthread_cleanup_pop(0);

#if DEBUG_CYGWIN_THREADS
	GC_printf2("thread 0x%x(0x%x) returned from start routine.\n",
		(int)pthread_self(), GetCurrentThreadId());
#endif

	return (result);
}

void GC_thread_exit_proc(void* arg)
{
	GC_thread me = (GC_thread)arg;
	int i;

#if DEBUG_CYGWIN_THREADS
	GC_printf2("thread 0x%x(0x%x) called pthread_exit().\n",
		(int)pthread_self(), GetCurrentThreadId());
#endif

	LOCK();
	if (me->flags & DETACHED)
	{
		GC_delete_thread(GetCurrentThreadId());
	}
	else
	{
		me->flags |= FINISHED;
	}
	UNLOCK();
}

int GC_pthread_sigmask(int how, const sigset_t* set, sigset_t* oset)
{
	return pthread_sigmask(how, set, oset);
}

int GC_pthread_detach(pthread_t thread)
{
	int result;
	GC_thread thread_gc_id;

	LOCK();
	thread_gc_id = GC_lookup_thread(thread);
	UNLOCK();
	result = pthread_detach(thread);
	if (result == 0)
	{
		LOCK();
		thread_gc_id->flags |= DETACHED;
		if (thread_gc_id->flags & FINISHED)
		{
			GC_delete_gc_thread(thread_gc_id);
		}
		UNLOCK();
	}
	return result;
}
#else
#if defined(GC_DLL) || defined(GC_INSIDE_DLL)
BOOL WINAPI GC_DllMain(HINSTANCE inst, ULONG reason, LPVOID reserved)
{
	switch(reason)
	{
	case DLL_PROCESS_ATTACH:
		GC_init();
	case DLL_THREAD_ATTACH:
		GC_ASSERT(GC_thr_initialized);
		if (GC_main_thread != GetCurrentThreadId())
		{
			GC_new_thread();
		}
		break;
	case DLL_THREAD_DETACH:
		GC_delete_thread(GetCurrentThreadId());
		break;
	case DLL_PROCESS_DETACH:
		{
			int i;

			LOCK();
			for (i = 0; i <= GC_max_thread_index(); ++i)
			{
				if (thread_table[i].in_use)
					GC_delete_gc_thread(thread_table + i);
			}
			UNLOCK();

			GC_deinit();
			DeleteCriticalSection(&GC_allocate_ml);
		}
		break;
	}
	return TRUE;
}
#endif
#endif
#endif
#endif

